{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building Tilesets using Xarray-Spatial and Datashader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Xarray-Spatial provides `render_tiles` which is a utility function for creating tilesets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import datashader as ds\n",
    "import numpy as np\n",
    "\n",
    "from xrspatial.tiles import render_tiles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import geopandas as gpd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "world = gpd.read_file(\n",
    "    gpd.datasets.get_path('naturalearth_lowres')\n",
    ")\n",
    "world = world.to_crs(\"EPSG:3857\") \n",
    "world = world[world.continent != 'Antarctica']\n",
    "world.plot(figsize=(7, 7))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define Tiling Component Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create `load_data_func`\n",
    "- accepts `x_range` and `y_range` arguments which correspond to the ranges of the supertile being rendered.\n",
    "- returns a dataframe-like object (pd.Dataframe / dask.Dataframe)\n",
    "- this example `load_data_func` creates a pandas dataframe with `x` and `y` fields sampled from a wald distribution "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "def load_data_func(x_range, y_range):\n",
    "    return world.cx[y_range[0]:y_range[1], x_range[0]:x_range[1]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create `rasterize_func`\n",
    "- accepts `df`, `x_range`, `y_range`, `height`, `width` arguments which correspond to the data, ranges, and plot dimensions of the supertile being rendered.\n",
    "- returns an `xr.DataArray` object representing the aggregate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datashader as ds\n",
    "from spatialpandas import GeoDataFrame\n",
    "\n",
    "def rasterize_func(df, x_range, y_range, height, width):\n",
    "    spatialpandas_df = GeoDataFrame(df, geometry='geometry')\n",
    "    # aggregate\n",
    "    cvs = ds.Canvas(x_range=x_range, y_range=y_range,\n",
    "                    plot_height=height, plot_width=width)\n",
    "    agg = cvs.polygons(spatialpandas_df, 'geometry')\n",
    "    return agg"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create `shader_func`\n",
    "- accepts `agg (xr.DataArray)`, `span (tuple(min, max))`.  The span argument can be used to control color mapping / auto-ranging across supertiles.\n",
    "- returns an `ds.Image` object representing the shaded image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datashader.transfer_functions as tf\n",
    "from datashader.colors import viridis\n",
    "\n",
    "def shader_func(agg, span=None):\n",
    "    img = tf.shade(agg, cmap=['black', 'teal'], span=span, how='log')\n",
    "    img = tf.set_background(img, 'black')\n",
    "    return img"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create `post_render_func`\n",
    "- accepts `img `, `extras` arguments which correspond to the output PIL.Image before it is write to disk (or S3), and addtional image properties.\n",
    "- returns image `(PIL.Image)`\n",
    "- this is a good place to run any non-datashader-specific logic on each output tile."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def post_render_func(img, **kwargs):\n",
    "    info = \"x={},y={},z={}\".format(kwargs['x'], kwargs['y'], kwargs['z'])\n",
    "    return img"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Render tiles to local filesystem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "full_extent_of_data = (-20e6, 20e6,\n",
    "                       -20e6, 20e6)\n",
    "\n",
    "output_path = '/Users/bcollins/temp/test_world/'\n",
    "results = render_tiles(full_extent_of_data,\n",
    "                       range(0, 8),\n",
    "                       load_data_func=load_data_func,\n",
    "                       rasterize_func=rasterize_func,\n",
    "                       shader_func=shader_func,\n",
    "                       post_render_func=post_render_func,\n",
    "                       output_path=output_path,\n",
    "                       color_ranging_strategy=(0,2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Preview the tileset using Bokeh\n",
    "- Browse to the tile output directory and start an http server:\n",
    "\n",
    "```bash\n",
    "$> cd test_tiles_output\n",
    "$> python -m http.server\n",
    "\n",
    "Starting up http-server, serving ./\n",
    "Available on:\n",
    "  http://127.0.0.1:8080\n",
    "  http://192.168.1.7:8080\n",
    "Hit CTRL-C to stop the server\n",
    "```\n",
    "\n",
    "- build a `bokeh.plotting.Figure`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.io import output_notebook, show, output_file\n",
    "output_notebook()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Preview Tiles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xmin, ymin, xmax, ymax = full_extent_of_data\n",
    "from bokeh.plotting import figure\n",
    "from bokeh.models.tiles import WMTSTileSource\n",
    "\n",
    "p = figure(width=800, height=800, \n",
    "           x_range=(int(-20e6), int(20e6)),\n",
    "           y_range=(int(-20e6), int(20e6)),\n",
    "           tools=\"pan,wheel_zoom,reset\")\n",
    "p.axis.visible = False\n",
    "p.background_fill_color = 'black'\n",
    "p.grid.grid_line_alpha = 0\n",
    "p.add_tile(WMTSTileSource(url=\"http://localhost:10000/{Z}/{X}/{Y}.png\"),\n",
    "           render_parents=False)\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
